package com.study.chunguard.common.utils;

import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.interfaces.Compare;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.study.chunguard.common.enums.Direction;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

import java.beans.PropertyDescriptor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * MyBatis-Plus QueryWrapper 条件构造器,从查询对象中构建查询条件
 * 条件构造遵循以下约定：
 * <p>
 * 1.解析规则
 * <p>
 * 1.1.查询对象字段值非空（不是null和空字符串）才会加入查询条件
 * <p>
 * 1.2.查询对象字段对应的数据库字段，通过对应注解 @TableField(value = "client_id") 的value提取
 * <p>
 * 1.3.查询对象字段值类型是 Direction，用order by片段，如 Direction detectionTimeOrder = Direction.DESC 会转换为 order by detection_time desc
 * <p>
 * 1.4.查询对象字段值类型是 {@link Collection}，用in片段，如果以not开头或结尾，使用not in片段如 List<> notSiteCodes 会转换为 site_code not in (?,?,?),否则使用in片段如 List<> siteCodes 会转换为 site_code in (?,?,?)
 * <p>
 * 1.5.查询对象字段名以start|lower|min|from开头或结尾，表示范围的下限，用>=片段，如 detectionTimeStart 会转换为 detection_time >= ?
 * <p>
 * 1.6.查询对象字段名以end|upper|max|to开头或结尾，表示范围的上限，用<=片段，如 detectionTimeEnd 会转换为 detection_time <= ?
 * <p>
 * 1.7.查询对象字段名以startEc|lowerEc|minEc|fromEc开头或结尾，Ec约定为‘exclusive’的缩写，表示范围的下限，但是不包含当前值，用>片段，如 detectionTimeStartEc 会转换为 detection_time > ?
 * <p>
 * 1.8.查询对象字段名以endEc|upperEc|maxEc|toEc开头或结尾，Ec约定为‘exclusive’的缩写，表示范围的上限，但是不包含当前值，用<片段，如 detectionTimeEndEc 会转换为 detection_time < ?
 * <p>
 * 1.9.prefix|suffix开头或结尾，表示前缀或后缀查询，用like片段，如 vehPlatePrefix 会转换为 veh_plate like ?% 或 vehPlateSuffix 会转换为 veh_plate like %?
 * <p>
 * 1.10.查询对象字段名以notLike|notContains|notMatches|notMatch开头或结尾，表示否定模糊查询，用not like片段，如 vehPlateNotLike 会转换为 veh_plate not like %?%
 * <p>
 * 1.11.查询对象字段名以like|contains|matches|match开头或结尾，表示模糊查询，用like片段，如 vehPlateLike 会转换为 veh_plate like %?%
 * <p>
 * 1.12.查询对象字段名以fulltext开头或结尾，表示全文检索查询，用MATCH() AGAINST()片段，如 vehPlateFullText 会转换为 MATCH(veh_plate) AGAINST(?)
 * <p>
 * 1.13.查询对象字段名以not开头或结尾，表示否定查询，用not片段，如 vehPlateNot 会转换为 veh_plate != ?
 * <p>
 * 1.14.其他情况表示精确查询，用=片段，如busiNum 会转换为busi_num = ?
 * <p>
 * 2.OR嵌套查询列表
 * <p>
 * 需要构建OR嵌套查询列表，解析规则同上，解析结果为嵌套查询，最终将转换为 and (site_code in (?,?,?) or detection_time >= ? or ...)
 *
 * @author zhaowenhao
 * @since 2022-12-27
 */
@SuppressWarnings("AlibabaMethodTooLong")
public class QueryWrapperBuilder {

    /**
     * Utility classes should not have a public or default constructor.
     */
    private QueryWrapperBuilder() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 范围开始（不包含当前值）正则表达式
     */
    public static final Pattern EXCLUSIVE_RANGE_START_PATTERN = Pattern.compile("(.+(start|lower|min|from)ec)|((start|lower|min|from)ec.+)", Pattern.CASE_INSENSITIVE);

    /**
     * 范围结束（不包含当前值）正则表达式
     */
    public static final Pattern EXCLUSIVE_RANGE_END_PATTERN = Pattern.compile("(.+(end|upper|max|to)ec)|((end|upper|max|to)ec.+)", Pattern.CASE_INSENSITIVE);

    /**
     * 范围开始正则表达式
     */
    public static final Pattern RANGE_START_PATTERN = Pattern.compile("(.+(start|lower|min|from))|((start|lower|min|from).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 范围结束正则表达式
     */
    public static final Pattern RANGE_END_PATTERN = Pattern.compile("(.+(end|upper|max|to))|((end|upper|max|to).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 前缀查询
     */
    public static final Pattern PREFIX_PATTERN = Pattern.compile("(.+(prefix))|((prefix).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 后缀查询
     */
    public static final Pattern SUFFIX_PATTERN = Pattern.compile("(.+(suffix))|((suffix).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 模糊查询
     */
    public static final Pattern LIKE_PATTERN = Pattern.compile("(.+(like|contains|matches|match))|((like|contains|matches|match).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 否定模糊查询
     */
    public static final Pattern NOT_LIKE_PATTERN = Pattern.compile("(.+(not)(like|contains|matches|match))|((not)(like|contains|matches|match).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 全文检索查询
     */
    public static final Pattern FULL_TEXT_SEARCH_PATTERN = Pattern.compile("(.+(fulltext))|((fulltext).+)", Pattern.CASE_INSENSITIVE);

    /**
     * 不等于查询
     */
    public static final Pattern NOT_PATTERN = Pattern.compile("(.+(not))|((not).+)", Pattern.CASE_INSENSITIVE);

    /**
     * AES解密查询（索引会失效）
     */
    public static final Pattern AES_DECRYPT_PATTERN = Pattern.compile("(.+(decrypt))|((decrypt).+)", Pattern.CASE_INSENSITIVE);

    /**
     * AES解密模糊查询（索引会失效）
     */
    public static final Pattern AES_DECRYPT_LIKE_PATTERN = Pattern.compile("(.+(decryptFuzzy))|((decryptFuzzy).+)", Pattern.CASE_INSENSITIVE);


    @FunctionalInterface
    public interface QueryStrategy {
        default void apply(QueryWrapper<?> queryWrapper, String column, Object value, boolean isOr) {
            if (isOr) {
                queryWrapper.or();
            }
            apply(queryWrapper, column, value);
        }

        void apply(QueryWrapper<?> wrapper, String column, Object value);
    }

    // region 封装正则表达式Map和策略Map
    private static final Map<String, QueryStrategy> STRATEGY_MAP = new HashMap<>();
    private static final Map<String, Pattern> PATTERN_MAP = new HashMap<>();

    public static final String RANGE_START_PATTERN_KEY = "RANGE_START_PATTERN";

    public static final String RANGE_END_PATTERN_KEY = "RANGE_END_PATTERN";

    public static final String EXCLUSIVE_RANGE_START_PATTERN_KEY = "EXCLUSIVE_RANGE_START_PATTERN";

    public static final String EXCLUSIVE_RANGE_END_PATTERN_KEY = "EXCLUSIVE_RANGE_END_PATTERN";

    public static final String PREFIX_PATTERN_KEY = "PREFIX_PATTERN";

    public static final String SUFFIX_PATTERN_KEY = "SUFFIX_PATTERN";

    public static final String NOT_LIKE_PATTERN_KEY = "NOT_LIKE_PATTERN";

    public static final String LIKE_PATTERN_KEY = "LIKE_PATTERN";

    public static final String FULL_TEXT_SEARCH_PATTERN_KEY = "FULL_TEXT_SEARCH_PATTERN";

    public static final String NOT_PATTERN_KEY = "NOT_PATTERN";

    public static final String AES_DECRYPT_PATTERN_KEY = "AES_DECRYPT_PATTERN";

    public static final String AES_DECRYPT_LIKE_PATTERN_KEY = "AES_DECRYPT_LIKE_PATTERN";

    static {
        PATTERN_MAP.put(RANGE_START_PATTERN_KEY, RANGE_START_PATTERN);
        PATTERN_MAP.put(RANGE_END_PATTERN_KEY, RANGE_END_PATTERN);
        PATTERN_MAP.put(EXCLUSIVE_RANGE_START_PATTERN_KEY, EXCLUSIVE_RANGE_START_PATTERN);
        PATTERN_MAP.put(EXCLUSIVE_RANGE_END_PATTERN_KEY, EXCLUSIVE_RANGE_END_PATTERN);
        PATTERN_MAP.put(PREFIX_PATTERN_KEY, PREFIX_PATTERN);
        PATTERN_MAP.put(SUFFIX_PATTERN_KEY, SUFFIX_PATTERN);
        PATTERN_MAP.put(NOT_LIKE_PATTERN_KEY, NOT_LIKE_PATTERN);
        PATTERN_MAP.put(LIKE_PATTERN_KEY, LIKE_PATTERN);
        PATTERN_MAP.put(FULL_TEXT_SEARCH_PATTERN_KEY, FULL_TEXT_SEARCH_PATTERN);
        PATTERN_MAP.put(NOT_PATTERN_KEY, NOT_PATTERN);
        PATTERN_MAP.put(AES_DECRYPT_PATTERN_KEY, AES_DECRYPT_PATTERN);
        PATTERN_MAP.put(AES_DECRYPT_LIKE_PATTERN_KEY, AES_DECRYPT_LIKE_PATTERN);

        STRATEGY_MAP.put(RANGE_START_PATTERN_KEY, Compare::ge);
        STRATEGY_MAP.put(RANGE_END_PATTERN_KEY, Compare::le);
        STRATEGY_MAP.put(EXCLUSIVE_RANGE_START_PATTERN_KEY, Compare::gt);
        STRATEGY_MAP.put(EXCLUSIVE_RANGE_END_PATTERN_KEY, Compare::lt);
        STRATEGY_MAP.put(PREFIX_PATTERN_KEY, Compare::likeRight);
        STRATEGY_MAP.put(SUFFIX_PATTERN_KEY, Compare::likeLeft);
        STRATEGY_MAP.put(NOT_LIKE_PATTERN_KEY, Compare::notLike);
        STRATEGY_MAP.put(LIKE_PATTERN_KEY, Compare::like);
        STRATEGY_MAP.put(FULL_TEXT_SEARCH_PATTERN_KEY, (queryWrapper, column, value) -> queryWrapper.apply("MATCH(" + column + ") AGAINST({0})", value));
        STRATEGY_MAP.put(NOT_PATTERN_KEY, Compare::ne);
        STRATEGY_MAP.put(AES_DECRYPT_PATTERN_KEY, (queryWrapper, column, value) -> {
            String key = AesUtil.AES_KEY;
            String decryptedColumn = "AES_DECRYPT(UNHEX(" + column + "),'" + key + "')";
            String sql = "CAST(binary(" + decryptedColumn + ") AS CHAR CHARACTER SET UTF8) = {0}";
            queryWrapper.apply(sql, value);
        });
        STRATEGY_MAP.put(AES_DECRYPT_LIKE_PATTERN_KEY, (queryWrapper, column, value) -> {
            String key = AesUtil.AES_KEY;
            String decryptedColumn = "AES_DECRYPT(UNHEX(" + column + "),'" + key + "')";
            String sql = "CAST(binary(" + decryptedColumn + ") AS CHAR CHARACTER SET UTF8) LIKE {0}";
            queryWrapper.apply(sql, "%" + value + "%");
        });
    }
    // endregion

    @SafeVarargs
    public static <Q, T> QueryWrapper<T> build(Q andQuery, Q... orQueryArray) {
        if (andQuery == null) return null;

        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        applyQuery(queryWrapper, andQuery, false);

        Optional.ofNullable(orQueryArray).ifPresent(array -> Arrays.stream(array)
                .filter(Objects::nonNull)
                .forEach(orQuery -> queryWrapper.and(qw -> applyQuery(qw, orQuery, true))));

        return queryWrapper;
    }

    private static <Q, T> void applyQuery(QueryWrapper<T> queryWrapper, Q query, boolean isOr) {
        Class<?> aClass = query.getClass();
        PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(aClass);
        final BeanWrapperImpl wrapper = new BeanWrapperImpl(query);

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            String column = getColumn(aClass, propertyDescriptor);
            String propertyName = propertyDescriptor.getName();
            Object propertyValue = wrapper.getPropertyValue(propertyName);
            // 先处理特殊值：排序标志和集合
            if (propertyValue instanceof Direction) {
                applyOrder(queryWrapper, column, propertyValue);
            } else if (propertyValue instanceof Collection<?>) {
                applyCollection(queryWrapper, column, propertyName, (Collection<?>) propertyValue);
            } else if (propertyValue != null && CharSequenceUtil.isNotBlank(propertyValue.toString())) {
                applyStrategy(queryWrapper, column, propertyName, propertyValue, isOr);
            }
        }
    }

    private static <T> void applyOrder(QueryWrapper<T> queryWrapper, String column, Object value) {
        if (Direction.ASC.equals(value)) {
            queryWrapper.orderByAsc(column);
        } else {
            queryWrapper.orderByDesc(column);
        }
    }

    private static <T> void applyCollection(QueryWrapper<T> queryWrapper, String column, String propertyName, Collection<?> values) {
        if (PATTERN_MAP.get(NOT_PATTERN_KEY).matcher(propertyName).matches()) {
            queryWrapper.notIn(column, values);
        } else {
            queryWrapper.in(column, values);
        }
    }

    private static <T> void applyStrategy(QueryWrapper<T> queryWrapper, String column, String propertyName, Object propertyValue, boolean isOr) {
        Optional<QueryStrategy> matchedStrategy = findMatchingStrategy(propertyName);
        if (matchedStrategy.isPresent() && !propertyName.equalsIgnoreCase(CharSequenceUtil.toCamelCase(column))) {
            matchedStrategy.get().apply(queryWrapper, column, propertyValue, isOr);
        } else {
            applyDefaultStrategy(queryWrapper, column, propertyValue, isOr);
        }
    }

    private static Optional<QueryStrategy> findMatchingStrategy(String propertyName) {
        return STRATEGY_MAP.entrySet().stream()
                .filter(entry -> {
                    Pattern pattern = PATTERN_MAP.get(entry.getKey());
                    return pattern != null && pattern.matcher(propertyName).matches();
                })
                .findFirst()
                .map(Map.Entry::getValue);
    }

    private static <T> void applyDefaultStrategy(QueryWrapper<T> queryWrapper, String column, Object value, boolean isOr) {
        if (isOr) {
            queryWrapper.or().eq(column, value);
        } else {
            queryWrapper.eq(column, value);
        }
    }

    // region 处理缓存的Class的PropertyDescriptor和column的映射关系

    static final ConcurrentMap<Class<?>, CachedColumnResults> CLASS_CACHED_COLUMN_RESULTS_MAP =
            new ConcurrentHashMap<>(128);
    private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};

    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
        return forClass(clazz).getPropertyDescriptors();
    }

    @Nullable
    public static String getColumn(Class<?> clazz, PropertyDescriptor pd) throws BeansException {
        return forClass(clazz).getColumn(pd);
    }

    static CachedColumnResults forClass(Class<?> beanClass) throws BeansException {
        CachedColumnResults results = CLASS_CACHED_COLUMN_RESULTS_MAP.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedColumnResults(beanClass);

        CachedColumnResults existing = CLASS_CACHED_COLUMN_RESULTS_MAP.putIfAbsent(beanClass, results);
        return (existing != null ? existing : results);
    }

    public static final class CachedColumnResults {

        private final Map<PropertyDescriptor, String> pdToColumnMap;

        private CachedColumnResults(Class<?> beanClass) throws BeansException {
            pdToColumnMap = Arrays.stream(beanClass.getDeclaredFields())
                    .filter(field -> field.isAnnotationPresent(TableField.class))
                    .collect(Collectors.toMap(field -> BeanUtils.getPropertyDescriptor(beanClass, field.getName()),
                            field -> field.getAnnotation(TableField.class).value()));
        }

        @Nullable
        String getColumn(PropertyDescriptor pd) {
            return pdToColumnMap.get(pd);
        }

        PropertyDescriptor[] getPropertyDescriptors() {
            return this.pdToColumnMap.keySet().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
        }

    }
    // endregion
}
